package com.ag.improvedjava.iterable;

import static com.ag.improvedjava.utils.predicates.PredicatesForNumbers.POSITIVE;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeMap;

import com.ag.improvedjava.exceptions.OverflowException;
import com.ag.improvedjava.utils.Function;
import com.ag.improvedjava.utils.Function2;
import com.ag.improvedjava.utils.Grouping;
import com.ag.improvedjava.utils.Lookup;
import com.ag.improvedjava.utils.OrderedIterable;
import com.ag.improvedjava.utils.Predicate;
import com.ag.improvedjava.utils.Requires;
import com.ag.improvedjava.utils.comparators.Comparators;
import com.ag.improvedjava.utils.comparators.ReverseComparator;
import com.ag.improvedjava.utils.functions.Functions;

public class Iterables {

	public static <T> ImprovedIterable<T> convert(Iterable<T> iterable) {
		Requires.requireNonNull(iterable, "The iterable argument can not be null.");
		return new ImprovedIterableImpl<T>(iterable);
	}

	@SafeVarargs
	public static <T> ImprovedIterable<T> convert(T...t) {
		Requires.requireNonNull(t);
		return new ImprovedIterableImpl<T>(Arrays.asList(t));
	}

	/***
	 * Returns an {@link ImprovedIterable} of incremented {@link Integer}, starting
	 * at {@link start} and finishing at ({@link start} + {@link count} - 1). 
	 * The iteration stops when the count is finished, or when the aggregated value (start+count) 
	 * reaches Integer.MAX_VALUE, whichever comes first.
	 * @param start
	 * @param count
	 * @return
	 */
	public static ImprovedIterable<Integer> range(int start, int count) {
		Requires.requireCondition(count, POSITIVE, "The count parameter can not be negative.");
		return new ImprovedIterableImpl<Integer>(new RangeIterable(start, count));
	}
	
	/***
	 * Returns an empty {@link ImprovedIterable} of {@link T}.  
	 * @return
	 */
	public static <T> ImprovedIterable<T> empty() {  
		return new ImprovedIterableImpl<T>(Collections.<T>emptyList());
	}
	
	/***
	 * Returns an {@link ImprovedIterable} containing {@link count} copies of {@link T}.  
	 * @return
	 */
	public static <T> ImprovedIterable<T> repeat(T element, int count) {
		Requires.requireCondition(count, POSITIVE);
		return new ImprovedIterableImpl<T>(new RepeatIterable<>(element, count));
	}

	static class ImprovedIterableImpl<E> implements ImprovedIterable<E> {

		Iterable<E> iterable;

		ImprovedIterableImpl(Iterable<E> iterable) {
			this.iterable = iterable;
		}

		@Override
		public Iterator<E> iterator() {
			return iterable.iterator();
		}

		@Override
		public ImprovedIterable<E> where(Predicate<E> predicate) {
			Requires.requireNonNull(predicate,
					"The where predicate can not be null.");
			return new ImprovedIterableImpl<>(new WhereIterable<>(this,
					predicate));
		}

		@Override
		public <R> ImprovedIterable<R> select(Function<E, R> transform) {
			Requires.requireNonNull(transform,
					"The transform function can not be null.");
			return new ImprovedIterableImpl<>(new SelectIterable<>(this,
					transform));
		}

		@Override
		public <R> ImprovedIterable<R> selectMany(Function<E, Iterable<R>> transform) {
			Requires.requireNonNull(transform,
					"The transform function can not be null.");
			return new ImprovedIterableImpl<>(new SelectManyIterable<E, R, R>(this, transform));
		}
		
		@Override
		public <U, R> ImprovedIterable<R> selectMany(
				Function<E, Iterable<U>> transform,
				Function2<E, U, R> resultSelector) {
			Requires.requireNonNull(transform,
					"The transform function can not be null.");
			Requires.requireNonNull(resultSelector, "The result selector can not be null");
			return new ImprovedIterableImpl<>(new SelectManyIterable<>(this, transform, resultSelector));
		}

		@Override
		public ImprovedIterable<E> concat(Iterable<E> iterable) {
			Requires.requireNonNull(iterable, "The iterable can not be null.");
			return new ImprovedIterableImpl<>(new ConcatIterable<>(this, iterable));
		}
		
		@Override
		public int count() {
			int count = 0;
			for (Iterator<E> iterator = this.iterator(); iterator.hasNext();) {
				iterator.next();
				if(++count == Integer.MIN_VALUE) {
					throw new OverflowException("The iterable contains more than Integer.MAX_VALUE elements.");
				}
			}
			return count;
		}
		
		@Override
		public int count(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null.");
			int count = 0;
			for (Iterator<E> iterator = this.iterator(); iterator.hasNext();) {
				if (predicate.evaluate(iterator.next())) {
					if(++count == Integer.MIN_VALUE) {
						throw new OverflowException("The iterable contains more than Integer.MAX_VALUE elements.");
					}
				}
			}
			return count;
		}
		
		@Override
		public long longCount() {
			long count = 0;
			for (Iterator<E> iterator = this.iterator(); iterator.hasNext();) {
				iterator.next();
				count++;
			}
			return count;
		}
		
		@Override
		public long longCount(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null.");
			long count = 0;
			for (Iterator<E> iterator = this.iterator(); iterator.hasNext();) {
				if (predicate.evaluate(iterator.next())) {
					count++;
				}
			}
			return count;
		}

		@Override
		public boolean any() {
			return iterator().hasNext();
		}

		@Override
		public boolean any(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null.");
			for (Iterator<E> iter = iterator() ; iter.hasNext() ; ) {
				if (predicate.evaluate(iter.next())) {
					return true;
				}
			}
			return false;
		}

		@Override
		public boolean all(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null.");
			for (Iterator<E> iter = iterator() ; iter.hasNext() ; ) {
				if (!predicate.evaluate(iter.next())) {
					return false;
				}
			}
			return true;
		}

		@Override
		public E first() {
			Iterator<E> iterator = iterator();
			if (!iterator.hasNext()) {
				throw new NoSuchElementException();
			}
			return iterator.next();
		}

		@Override
		public E first(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null.");
			for (Iterator<E> iter = iterator(); iter.hasNext() ; ) {
				E elem = iter.next();
				if (predicate.evaluate(elem)) {
					return elem;
				}
			}
			throw new NoSuchElementException();
		}

		@Override
		public E last() {
			Iterator<E> iterator = iterator();
			if (!iterator.hasNext()) {
				throw new NoSuchElementException();
			}
			E elem = iterator.next();
			while (iterator.hasNext()) {
				elem = iterator.next();
			}
			return elem;
		}
		
		@Override
		public E last(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null.");
			E elem = null;
			boolean found = false;
			for (Iterator<E> iter = iterator(); iter.hasNext() ; ) {
				E next = iter.next();
				if (predicate.evaluate(next)) {
					elem = next;
					found = true;
				}
			}
			if (found){
				return elem;
			}
			throw new NoSuchElementException();
		}

		@Override
		public E single() {
			Iterator<E> iterator = iterator();
			if (!iterator.hasNext()) {
				throw new NoSuchElementException();
			}
			E elem = iterator.next();
			if (iterator.hasNext()) {
				throw new NoSuchElementException();
			}
			return elem;
		}
		
		@Override
		public E single(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null.");
			E elem = null;
			boolean alreadyFound = false;
			for (Iterator<E> iter = iterator(); iter.hasNext() ; ) {
				E next = iter.next();
				if (predicate.evaluate(next)) {
					if (alreadyFound) {
						throw new NoSuchElementException();
					}
					elem = next;
					alreadyFound = true;
				}
			}
			if (alreadyFound){
				return elem;
			}
			throw new NoSuchElementException();
		}

		@Override
		public ImprovedIterable<E> defaultIfEmpty(E element) {
			return any() ? this : new ImprovedIterableImpl<>(new SingleElementIterable<>(element));
		}
		
		@Override
		public E aggregate(Function2<E, E, E> aggregator) {
			Requires.requireNonNull(aggregator);
			if (!any()) {
				throw new NoSuchElementException("The iterable can not be empty.");
			}
			Iterator<E> iter = iterator();
			E aggregatedValue = iter.next();
			while (iter.hasNext()) {
				aggregatedValue = aggregator.execute(aggregatedValue, iter.next());
			}
			return aggregatedValue;
		}
		
		@Override
		public <R> R aggregate(R seed, Function2<R, E, R> aggregator) {
			Requires.requireNonNull(aggregator);
			if (!any()) {
				throw new NoSuchElementException("The iterable can not be empty.");
			}
			R aggregatedValue = seed;
			for (E elem : this) {
				aggregatedValue = aggregator.execute(aggregatedValue, elem);
			}
			return aggregatedValue;
		}

		@Override
		public List<E> toList() {
			List<E> list = new ArrayList<E>();
			for (E e : this) {
				list.add(e);
			}
			return list;
		}
		
		@Override
		public Object[] toArray() {
			return toList().toArray();
		}

		@Override
		public String toString() {
			Iterator<E> it = iterator();
			if (!it.hasNext())
				return "[]";

			StringBuilder sb = new StringBuilder();
			sb.append('[');
			for (;;) {
				E e = it.next();
				sb.append(e == this ? "(this Collection)" : e);
				if (!it.hasNext())
					return sb.append(']').toString();
				sb.append(',').append(' ');
			}
		}

		@Override
		public ImprovedIterable<E> distinct() {
			return new ImprovedIterableImpl<>(new DistinctIterable<>(this));
		}

		@Override
		public ImprovedIterable<E> distinct(Comparator<E> comparer) {
			return new ImprovedIterableImpl<>(new DistinctIterable<>(this, comparer));
		}

		@Override
		public ImprovedIterable<E> union(Iterable<E> iterable) {
			Requires.requireNonNull(iterable, "The iterable can not be null.");
			return new ImprovedIterableImpl<>(new UnionIterable<>(this, iterable));
		}

		@Override
		public ImprovedIterable<E> union(Iterable<E> iterable,
				Comparator<E> comparer) {
			Requires.requireNonNull(iterable, "The iterable can not be null.");
			return new ImprovedIterableImpl<>(new UnionIterable<>(this, iterable, comparer));
		}

		@Override
		public ImprovedIterable<E> intersect(Iterable<E> iterable) {
			Requires.requireNonNull(iterable, "The iterable can not be null.");
			return new ImprovedIterableImpl<>(new IntersectIterable<>(this, iterable));
		}

		@Override
		public ImprovedIterable<E> intersect(Iterable<E> iterable,
				Comparator<E> comparer) {
			Requires.requireNonNull(iterable, "The iterable can not be null.");
			return new ImprovedIterableImpl<>(new IntersectIterable<>(this, iterable, comparer));
		}

		@Override
		public ImprovedIterable<E> except(Iterable<E> iterable) {
			Requires.requireNonNull(iterable, "The iterable can not be null.");
			return new ImprovedIterableImpl<>(new ExceptIterable<>(this, iterable));
		}

		@Override
		public ImprovedIterable<E> except(Iterable<E> iterable,
				Comparator<E> comparer) {
			Requires.requireNonNull(iterable, "The iterable can not be null.");
			return new ImprovedIterableImpl<>(new ExceptIterable<>(this, iterable, comparer));
		}

		@Override
		public <K> Lookup<K, E> toLookup(Function<E, K> keySelector) {
			return toLookup(keySelector, Functions.<E>identity(), null);
		}

		@Override
		public <K> Lookup<K, E> toLookup(Function<E, K> keySelector,
				Comparator<K> comparer) {
			return toLookup(keySelector, Functions.<E>identity(), comparer);
		}

		@Override
		public <K, R> Lookup<K, R> toLookup(Function<E, K> keySelector,
				Function<E, R> elementSelector) {
			return toLookup(keySelector, elementSelector, null);
		}

		@Override
		public <K, R> Lookup<K, R> toLookup(Function<E, K> keySelector,
				Function<E, R> elementSelector, Comparator<K> comparer) {
			
			Requires.requireNonNull(keySelector, "The key selector can not be null");
			Requires.requireNonNull(elementSelector, "The element selector can not be null");

			LookupImpl<K, R> lookup = new LookupImpl<K, R>(comparer);

			for (E elem : this) {
				K key = keySelector.execute(elem);
				R value = elementSelector.execute(elem);
				lookup.add(key, value);
			}

			return lookup;
		}

		@Override
		public <U, K, R> ImprovedIterable<R> join(Iterable<U> inner,
				Function<E, K> outerKeySelector,
				Function<U, K> innerKeySelector,
				Function2<E, U, R> resultSelector) {
			return join(inner, outerKeySelector, innerKeySelector, resultSelector, null);
		}

		@Override
		public <K, U, R> ImprovedIterable<R> join(Iterable<U> inner,
				final Function<E, K> outerKeySelector,
				Function<U, K> innerKeySelector,
				Function2<E, U, R> resultSelector, Comparator<K> comparer) {
			Requires.requireNonNull(inner, "The inner iterable can not be null.");
			Requires.requireNonNull(outerKeySelector, "The outerKey selector can not be null.");
			Requires.requireNonNull(innerKeySelector, "The innerKey selector can not be null.");
			Requires.requireNonNull(resultSelector, "The resultSelector can not be null.");

			final Lookup<K, U> lookup = Iterables.convert(inner).toLookup(innerKeySelector, comparer);
			
			return this.selectMany(new Function<E, Iterable<U>>() {
				public Iterable<U> execute(E param) {
					return lookup.values(outerKeySelector.execute(param));
				}
			}, resultSelector);
		}

		@Override
		public <K> ImprovedIterable<Grouping<K, E>> groupBy(
				Function<E, K> keySelector) {
			return groupBy(keySelector, Functions.<E>identity(), (Comparator<K>)null);
		}

		@Override
		public <K> ImprovedIterable<Grouping<K, E>> groupBy(
				Function<E, K> keySelector, Comparator<K> comparer) {
			return groupBy(keySelector, Functions.<E>identity(), comparer);
		}

		@Override
		public <K, U> ImprovedIterable<Grouping<K, U>> groupBy(
				Function<E, K> keySelector, Function<E, U> elementSelector) {
			return groupBy(keySelector, elementSelector, (Comparator<K>)null);
		}

		@Override
		public <K, U> ImprovedIterable<Grouping<K, U>> groupBy(
				Function<E, K> keySelector, Function<E, U> elementSelector,
				Comparator<K> comparer) {
			Requires.requireNonNull(keySelector, "The key selector can not be null.");
			Requires.requireNonNull(elementSelector, "The element selector can not be null.");
			return new ImprovedIterableImpl<>(new GroupByIterable<>(this, keySelector, elementSelector, comparer));
		}

		@Override
		public <K, R> ImprovedIterable<R> groupBy(Function<E, K> keySelector,
				Function2<K, Iterable<E>, R> resultSelector) {
			return groupBy(keySelector, Functions.<E>identity(), resultSelector, null);
		}

		@Override
		public <K, R> ImprovedIterable<R> groupBy(Function<E, K> keySelector,
				Function2<K, Iterable<E>, R> resultSelector,
				Comparator<K> comparer) {
			return groupBy(keySelector, Functions.<E>identity(), resultSelector,  comparer);
		}

		@Override
		public <K, U, R> ImprovedIterable<R> groupBy(
				Function<E, K> keySelector, Function<E, U> elementSelector,
				Function2<K, Iterable<U>, R> resultSelector) {
			return groupBy(keySelector, elementSelector, resultSelector, null);
		}

		@Override
		public <K, U, R> ImprovedIterable<R> groupBy(
				Function<E, K> keySelector, Function<E, U> elementSelector,
				final Function2<K, Iterable<U>, R> resultSelector,
				Comparator<K> comparer) {
			Requires.requireNonNull(resultSelector, "The result selector can not be null.");
			return groupBy(keySelector, elementSelector, comparer).select(new Function<Grouping<K,U>, R>() {
				public R execute(Grouping<K, U> group) {
					return resultSelector.execute(group.key(), group);
				}
			});
		}

		@Override
		public <K, U, R> ImprovedIterable<R> groupJoin(Iterable<U> inner,
				Function<E, K> outerKeySelector,
				Function<U, K> innerKeySelector,
				Function2<E, Iterable<U>, R> resultSelector) {
			return groupJoin(inner, outerKeySelector, innerKeySelector, resultSelector, null);
		}

		@Override
		public <K, U, R> ImprovedIterable<R> groupJoin(Iterable<U> inner,
				Function<E, K> outerKeySelector,
				Function<U, K> innerKeySelector,
				Function2<E, Iterable<U>, R> resultSelector,
				Comparator<K> comparer) {
			Requires.requireNonNull(inner, "The inner iterable can not be null.");
			Requires.requireNonNull(outerKeySelector, "The outerKey selector can not be null.");
			Requires.requireNonNull(innerKeySelector, "The innerKey selector can not be null.");
			Requires.requireNonNull(resultSelector, "The resultSelector can not be null.");

			return new ImprovedIterableImpl<>(new GroupJoinIterable<>(this,
					outerKeySelector, inner, innerKeySelector, resultSelector,
					comparer));
		}

		@Override
		public ImprovedIterable<E> take(int count) {
			return new ImprovedIterableImpl<>(new TakeIterable<>(this, count));
		}

		@Override
		public ImprovedIterable<E> takeWhile(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null");
			return new ImprovedIterableImpl<>(new TakeWhileIterable<>(this, predicate));
		}

		@Override
		public ImprovedIterable<E> skip(int count) {
			return new ImprovedIterableImpl<>(new SkipIterable<>(this, count));
		}

		@Override
		public ImprovedIterable<E> skipWhile(Predicate<E> predicate) {
			Requires.requireNonNull(predicate, "The predicate can not be null");
			return new ImprovedIterableImpl<>(new SkipWhileIterable<>(this, predicate));
		}

		@Override
		public <K> Map<K, E> toDictionary(Function<E, K> keySelector) {
			return toDictionary(keySelector, Functions.<E>identity(), (Comparator<K>)null);
		}

		@Override
		public <K, U> Map<K, U> toDictionary(Function<E, K> keySelector,
				Function<E, U> elementSelector) {
			return toDictionary(keySelector, elementSelector, null);
		}

		@Override
		public <K> Map<K, E> toDictionary(Function<E, K> keySelector,
				Comparator<K> comparer) {
			return toDictionary(keySelector, Functions.<E>identity(), comparer);
		}

		@Override
		public <K, U> Map<K, U> toDictionary(Function<E, K> keySelector,
				Function<E, U> elementSelector, Comparator<K> comparer) {

			Requires.requireNonNull(keySelector, "The key selector can not be null");
			Requires.requireNonNull(elementSelector, "The element selector can not be null");
			
			Map<K, U> map = new TreeMap<>(comparer);
			for (E elem : this) {
				K key = keySelector.execute(elem);
				U value = elementSelector.execute(elem);
				map.put(key, value);
			}
			return map;
		}

		@Override
		public <K extends Comparable<? super K>> OrderedIterable<E> orderBy(Function<E, K> keySelector) {
			return orderBy(keySelector, Comparators.<K>Natural());
		}

		@Override
		public <K> OrderedIterable<E> orderBy(Function<E, K> keySelector,
				Comparator<K> comparer) {
			Requires.requireNonNull(keySelector, "The key selector can not be null.");
			Requires.requireNonNull(comparer, "The comparer can not be null.");
			return new OrderedIterableImpl<>(this, keySelector, comparer);
		}

		@Override
		public <K extends Comparable<? super K>> OrderedIterable<E> orderByDescending(
				Function<E, K> keySelector) {
			return orderByDescending(keySelector, Comparators.<K>Natural());
		}

		@Override
		public <K> OrderedIterable<E> orderByDescending(
				Function<E, K> keySelector, Comparator<K> comparer) {
			Requires.requireNonNull(keySelector, "The key selector can not be null.");
			Requires.requireNonNull(comparer, "The comparer can not be null.");
			comparer = new ReverseComparator<>(comparer);
			return new OrderedIterableImpl<>(this, keySelector, comparer);
		}

		@Override
		public ImprovedIterable<E> reverse() {
			return new ImprovedIterableImpl<>(new ReverseIterable<>(this));
		}

		@Override
		public E elementAt(int index) {
			return new ElementAt<>(this, index).value();
		}

		@Override
		public boolean contains(E value) {
			return new Contains<>(this, value).value();
		}

		@Override
		public boolean contains(E value, Comparator<E> comparer) {
			return new Contains<>(this, value, comparer).value();
		}

		@Override
		public <R> ImprovedIterable<R> cast(Class<R> c) {
			Requires.requireNonNull(c, "The class can not be null.");
			return new ImprovedIterableImpl<>(new CastIterable<E, R>(this, c));
		}

		@Override
		public <R> ImprovedIterable<R> ofType(Class<R> c) {
			Requires.requireNonNull(c, "The class can not be null.");
			return new ImprovedIterableImpl<>(new OfTypeIterable<E, R>(this, c));
		}

		@Override
		public boolean sequenceEqual(Iterable<E> iterable) {
			Requires.requireNonNull(iterable, "The input iterable can not be null.");
			return new SequenceEqual<>(this, iterable).value();
		}

		@Override
		public boolean sequenceEqual(Iterable<E> iterable,
				Comparator<E> comparer) {
			Requires.requireNonNull(iterable, "The input iterable can not be null.");
			return new SequenceEqual<>(this, iterable, comparer).value();
		}

		@Override
		public <U, R> ImprovedIterable<R> zip(Iterable<U> iterable,
				Function2<E, U, R> resultSelector) {
			Requires.requireNonNull(iterable, "The input iterable can not be null.");
			Requires.requireNonNull(resultSelector, "The result selector can not be null.");
			return new ImprovedIterableImpl<>(new ZipIterable<>(this, iterable, resultSelector));
		}

	}
}

		
		/*	public void forEach(Action<? super E> action) {
				for (E t : this) {
					action.execute(t);
				}
			}
			*/
			
	
